package indzcraft.calc;
import java.util.HashMap;


public class CalculatorSubtlety extends Calc {
	
	Stats stats;
	Glyphs glyphs;
	Race race;
	GearBuffs gearbuffs;
	Weapon mh;
	Weapon oh;
	
	ProcDB procDB = new ProcDB();
	
	// variables
	double reaction_time = 1;
	
	// some globals for easier use
	double PRECISION = Math.pow(10, -20);
	double mh_swings_per_sec = 0.;
	double oh_swings_per_sec = 0.;
	double total_crits = 0.;
	HashMap<String,Double> aps = new HashMap<String,Double>();
	HashMap<String,Double> dps = new HashMap<String,Double>();

	double bs_energycost;
	
	double find_weakness_uptime;
	double ambush_shadowstep_rate;
		
	public CalculatorSubtlety(Stats stats, Weapon mh, Weapon oh, Glyphs glyphs, Race race, GearBuffs gearbuffs)
	{
		super(stats,mh,oh,glyphs,race,gearbuffs);
		this.stats = stats;
		this.mh = mh;
		this.oh = oh;
		this.glyphs = glyphs;
		this.race = race;
		this.gearbuffs = gearbuffs;
		get_dps();
	}
	
	private double get_dps()
	{	
		double t13_2pc = this.gearbuffs.get("t13_2pc");
		bs_energycost = (60-60*0.2*6/31*t13_2pc)*(0.8+0.2/this.stats.get_style_hit_chance(race,mh))-20; // slaughter from the shadows
		double bs_crit_chance = this.stats.get_crit_chance()+0.3;
		if (bs_crit_chance > 1)
			bs_crit_chance = 1.;
		bs_energycost -= 5 * bs_crit_chance * this.glyphs.backstab;
		double evi_energycost = (35-35*0.2*6/31*t13_2pc)/this.stats.get_style_hit_chance(race,mh);
		
		double hemo_energycost = (35-35*0.2*6/31*t13_2pc)*(0.8+0.2/this.stats.get_style_hit_chance(race,mh))-2*3; // slaughter from the shadows
		double ambush_energycost = (60-60*0.2*6/31*t13_2pc)*(0.8+0.2/this.stats.get_style_hit_chance(race,mh))-20; // slaughter from the shadows
		
		// HAT
		double raid_crits_per_second = 5.;
		double hat_triggers_per_second = raid_crits_per_second * this.stats.talents.get("honor_among_thieves") / 3.;
        double hat_cp_gen = 1 / (5 - this.stats.talents.get("honor_among_thieves") + 1 / hat_triggers_per_second);
		
		while(true) {
			
			//double rup_energycost = (25-25*0.2*6/31*t13_2pc)/this.stats.get_style_hit_chance(race,mh);
			double cps_snd = 5;
			double snd_length = (6+6*this.glyphs.slice_and_dice) + 3 * cps_snd - this.reaction_time;
			
			double hemo_interval = 24.;
			//double backstab_interval = bs_energycost / get_energy_regen();
			
			double energy_return_for_hemo = bs_energycost - hemo_energycost;
			// use now energy_regen instead of get_energy_regen(), will getting better implemented in the future
			double energy_regen = get_energy_regen() + energy_return_for_hemo / hemo_interval;
			
			double cp_builder_interval = bs_energycost / energy_regen;
			double cp_per_cp_builder = 1 + cp_builder_interval * hat_cp_gen;
			
			double eviscerate_net_energy_cost = evi_energycost - 5 * 3/3*5;
			double eviscerate_net_cp_cost = 5 - .2 * this.stats.talents.get("ruthlessness") - eviscerate_net_energy_cost * hat_cp_gen / energy_regen;

			double cp_builders_per_eviscerate = eviscerate_net_cp_cost / cp_per_cp_builder;
			double total_eviscerate_cost = eviscerate_net_energy_cost + cp_builders_per_eviscerate * bs_energycost;
			double total_eviscerate_duration = total_eviscerate_cost / energy_regen;
			
			// recup
			//double recuperate_duration = 30;
			
			double recuperate_base_energy_cost = 30-30*0.2*6/31*t13_2pc;
            double recuperate_net_energy_cost = recuperate_base_energy_cost - 5 * 3/3*5;
            double recuperate_net_cp_cost = recuperate_net_energy_cost * hat_cp_gen / get_energy_regen();
            double cp_builders_under_previous_recuperate = .5 * total_eviscerate_duration / bs_energycost;
            double cp_gained_under_previous_recuperate = cp_builders_under_previous_recuperate * cp_per_cp_builder;
            double cp_needed_outside_recuperate = recuperate_net_cp_cost - cp_gained_under_previous_recuperate;
            double cp_builders_after_recuperate = cp_needed_outside_recuperate / cp_per_cp_builder;
            double energy_spent_after_recuperate = cp_builders_after_recuperate * bs_energycost + recuperate_net_energy_cost;

            double cycle_length = 30 + energy_spent_after_recuperate / get_energy_regen();
            double total_cycle_regen = 30 * energy_regen + energy_spent_after_recuperate;
            
            // snd
            double snd_build_time = total_eviscerate_duration / 2;
            double snd_base_cost = 25-25*0.2*6/31*t13_2pc;
            double snd_build_energy_for_cp_builders = 5 * 3/3*5 + energy_regen * snd_build_time - snd_base_cost;
            double cp_builders_per_snd = snd_build_energy_for_cp_builders / bs_energycost;
            double hat_cp_per_snd = snd_build_time * hat_cp_gen;
            
            double snd_size = .2 * this.stats.talents.get("ruthlessness") + hat_cp_per_snd + cp_builders_per_snd;
            double snd_duration = snd_length;
      
            double snd_per_cycle = cycle_length / snd_duration;
            
            double vanish_cooldown = 180 - 30 * this.stats.talents.get("elusiveness");
            double ambushes_from_vanish = 1. / (vanish_cooldown + this.reaction_time) + this.stats.talents.get("preparation") / (300. + this.reaction_time);
            find_weakness_uptime = 0;
            if (this.stats.talents.get("find_weakness") == 1)
                find_weakness_uptime = 10 * ambushes_from_vanish;
            
            double cp_per_ambush = 2 + .5 * this.stats.talents.get("initiative");
            
            double bonus_cp_per_cycle = (hat_cp_gen + ambushes_from_vanish * (cp_per_ambush + 2 * this.stats.talents.get("premeditation"))) * cycle_length;
            double cp_used_on_buffs = 5 + snd_size * snd_per_cycle - (1 + snd_per_cycle) * .2 * this.stats.talents.get("ruthlessness");
            double bonus_eviscerates = (bonus_cp_per_cycle - cp_used_on_buffs) / (5 - .2 * this.stats.talents.get("ruthlessness"));
            double energy_spent_on_bonus_finishers = 30 + 25 * snd_per_cycle + 35 * bonus_eviscerates - (5 + snd_size * snd_per_cycle + 5 * bonus_eviscerates) * 3/3*5 + cycle_length * ambushes_from_vanish * ambush_energycost;
            double energy_for_evis_spam = total_cycle_regen - energy_spent_on_bonus_finishers;
            double total_cost_of_extra_eviscerate = (5 - .2 * this.stats.talents.get("ruthlessness")) * bs_energycost + evi_energycost - 5 * 3/3*5;
            double extra_eviscerates_per_cycle = energy_for_evis_spam / total_cost_of_extra_eviscerate;
                 
            aps.put("backstab", (5 - .2 * this.stats.talents.get("ruthlessness")) * extra_eviscerates_per_cycle / cycle_length);
            aps.put("eviscerate", (bonus_eviscerates + extra_eviscerates_per_cycle) / cycle_length);
            aps.put("ambush", ambushes_from_vanish);
            
            double t13_4pc = this.gearbuffs.get("t13_4pc");
            ambush_shadowstep_rate = 1;
            if (this.stats.talents.get("shadow_dance") == 1){
                double shadow_dance_duration = 6. + 2 * this.glyphs.shadow_dance;
                shadow_dance_duration += t13_4pc * 2;
                double shadow_dance_frequency = 1. / (60 + this.reaction_time);

                double shadow_dance_bonus_cp_regen = shadow_dance_duration * hat_cp_gen + 2 * this.stats.talents.get("premeditation");
                double shadow_dance_bonus_eviscerates = shadow_dance_bonus_cp_regen / (5 - .2 * this.stats.talents.get("ruthlessness"));
                double shadow_dance_bonus_eviscerate_cost = shadow_dance_bonus_eviscerates * (35 - 5 * 3/3*5);
                double shadow_dance_available_energy = shadow_dance_duration * energy_regen - shadow_dance_bonus_eviscerate_cost;

                double shadow_dance_eviscerate_cost = (5 - .2 * this.stats.talents.get("ruthlessness")) / cp_per_ambush * ambush_energycost + (35 - 5 * 3/3*5);
                double shadow_dance_eviscerates_for_period = shadow_dance_available_energy / shadow_dance_eviscerate_cost;

                double base_bonus_cp_regen = shadow_dance_duration * hat_cp_gen;
                double base_bonus_eviscerates = base_bonus_cp_regen / (5 - .2 * this.stats.talents.get("ruthlessness"));
                double base_bonus_eviscerate_cost = base_bonus_eviscerates * (35 - 5 * 3/3*5);
                double base_available_energy = shadow_dance_duration * energy_regen - base_bonus_eviscerate_cost;

                double base_eviscerates_for_period = base_available_energy / total_cost_of_extra_eviscerate;

                double shadow_dance_extra_eviscerates = shadow_dance_eviscerates_for_period + shadow_dance_bonus_eviscerates - base_eviscerates_for_period - base_bonus_eviscerates;
                double shadow_dance_extra_ambushes = (5 - .2 * this.stats.talents.get("ruthlessness")) / cp_per_ambush * shadow_dance_eviscerates_for_period;
                double shadow_dance_replaced_cp_builders = (5 - .2 * this.stats.talents.get("ruthlessness")) * base_eviscerates_for_period;

                ambush_shadowstep_rate = (shadow_dance_frequency + ambushes_from_vanish) / (shadow_dance_extra_ambushes + ambushes_from_vanish);
                
                aps.put("backstab", aps.get("backstab")-shadow_dance_replaced_cp_builders * shadow_dance_frequency);
                aps.put("ambush", aps.get("ambush")+shadow_dance_extra_ambushes * shadow_dance_frequency);
                aps.put("eviscerate", aps.get("eviscerate")+shadow_dance_extra_eviscerates * shadow_dance_frequency);

                find_weakness_uptime += (10 + shadow_dance_duration - this.reaction_time) * shadow_dance_frequency;
            }
			aps.put("rupture", 0.5);
			
			// legendary evis
			double dagger_stage3 = this.gearbuffs.get("dagger_stage3");
			double avg_proc_at = 40; // stacks
			double proc_interval = 1/(0.275*total_hits);
			double proc_after = avg_proc_at*proc_interval;
			double procs_per_sec = 1./proc_after;
			double extra_evis_per_proc = 5;
			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
			aps.put("eviscerate", aps.get("eviscerate") + avg_extra_evis);
			
			mh_swings_per_sec = get_total_speed()/this.mh.speed;
			oh_swings_per_sec = get_total_speed()/this.oh.speed;
			double mh_hits_per_sec = get_total_speed()/this.mh.speed * this.stats.get_mh_hit_chance(race,mh);
			double oh_hits_per_sec = get_total_speed()/this.oh.speed * this.stats.get_oh_hit_chance(race,oh);
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			
			aps.put("hemorrhage", 1. / hemo_interval);
			aps.put("backstab", aps.get("backstab") - aps.get("hemorrhage"));
			
			double total_mh_hits = aps.get("mh") + aps.get("backstab") + aps.get("eviscerate") + aps.get("ambush") + aps.get("hemorrhage");
			double total_oh_hits = aps.get("oh");
			
			if (this.glyphs.hemorrhage == 1){
				 double ticks_per_second = Math.min(1. / 3, 8 / hemo_interval);
				 aps.put("hemorrhage_dot", ticks_per_second);
			}
			
            aps.put("deadly_poison", 1/3.);
			double extra_dp_procs = total_oh_hits * 0.3 * this.stats.get_spell_hit_chance();
			double ip_procs = this.stats.get_spell_hit_chance() * (total_mh_hits) * 0.2 * this.mh.speed / 1.4 + extra_dp_procs;
			aps.put("instant_poison", ip_procs);
			//System.out.println("3 " + Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)));
			//System.out.println(total_hits + " " + (total_mh_hits + total_oh_hits));
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			total_hits = total_mh_hits + total_oh_hits;
			total_crits = (aps.get("mh") + aps.get("oh")) * this.stats.get_crit_chance();
			//total_crits += aps.get("sinister_strike") * (this.stats.get_crit_chance() + 0.05);
			// recalculating procs etc.
			
			double mh_landslide_uptime = 1 - Math.pow(1 - this.mh.speed / 60, 12 * (total_mh_hits));
			if(!this.mh.hasEnchant("landslide"))
				mh_landslide_uptime *= 0;
			double oh_landslide_uptime = 1 - Math.pow(1 - this.oh.speed / 60, 12 * (total_oh_hits));
			if(!this.oh.hasEnchant("landslide"))
				oh_landslide_uptime *= 0;
			
			double mh_hurricane_uptime = 1 - Math.pow(1 - this.mh.speed / 60, 12 * (total_mh_hits));
			if(!this.mh.hasEnchant("hurricane"))
				mh_hurricane_uptime *= 0;
			double oh_hurricane_uptime = 1 - Math.pow(1 - this.oh.speed / 60, 12 * (total_oh_hits));
			if(!this.oh.hasEnchant("hurricane"))
				oh_hurricane_uptime *= 0;
			
			double key_to_the_endless_chamber = this.gearbuffs.get("key_to_the_endless_chamber");
			this.stats.procs_agi = 15/(75+1/(total_hits*0.1))*1710*key_to_the_endless_chamber; // kttec
			double left_eye_of_rajh = this.gearbuffs.get("left_eye_of_rajh");
			this.stats.procs_agi += 10/(50+1/(total_crits*0.5))*1710*left_eye_of_rajh; // leofr
			
			//legendary stage1
			double dagger_stage1 = this.gearbuffs.get("dagger_stage1");
			double dagger_stage2 = this.gearbuffs.get("dagger_stage2");
			dagger_stage3 = this.gearbuffs.get("dagger_stage3");
			double dagger_uptime = get_uptime_with_rampup(0.275,50,30);
			double dagger_uptime_3 = get_uptime_with_rampup_dagger3(0.095,40,30);
			this.stats.procs_agi += dagger_stage1 * dagger_uptime * 2 + dagger_stage2 * dagger_uptime * 5 + dagger_stage3 * dagger_uptime_3 * 17;
			
			//wrath of unchaining
			double wrath_of_unchaining = this.gearbuffs.get("wrath_of_unchaining");
			double wrath_of_unchaining_heroic = this.gearbuffs.get("wrath_of_unchaining_heroic");
			double wrath_of_unchaining_lfr = this.gearbuffs.get("wrath_of_unchaining_lfr");
			double wrath_of_unchaining_uptime = get_uptime(1,10,10);
			this.stats.procs_agi += wrath_of_unchaining * wrath_of_unchaining_uptime * 88 + wrath_of_unchaining_heroic * wrath_of_unchaining_uptime * 99 + wrath_of_unchaining_lfr * wrath_of_unchaining_uptime * 78;
			
			// kiroptyric_sigil
			double kiroptyric_sigil = this.gearbuffs.get("kiroptyric_sigil");
			double kiroptyric_sigil_uptime = 15/(90+this.reaction_time);
			this.stats.procs_agi += kiroptyric_sigil * kiroptyric_sigil_uptime * 2290;
			
			this.stats.procs_ap = mh_landslide_uptime*1000 + oh_landslide_uptime*1000;
			
			this.stats.procs_haste = mh_hurricane_uptime*450+oh_hurricane_uptime*450;
			double prestors_talisman_of_machination = this.gearbuffs.get("prestors_talisman_of_machination");
			this.stats.procs_haste += 15/(75+1/(total_hits*0.1))*1926*prestors_talisman_of_machination;
			
			double matrix_restabilizer = this.gearbuffs.get("matrix_restabilizer");
			double matrix_restabilizer_heroic = this.gearbuffs.get("matrix_restabilizer_heroic");
			this.stats.procs_haste += this.procDB.get("matrix_restabilizer").boost(total_hits)*matrix_restabilizer;
			this.stats.procs_haste += this.procDB.get("matrix_restabilizer_heroic").boost(total_hits)*matrix_restabilizer_heroic;
			
			double arrow_of_time = this.gearbuffs.get("arrow_of_time");
			this.stats.procs_haste += this.procDB.get("arrow_of_time").boost(total_hits)*arrow_of_time;
			
			double the_hungerer = this.gearbuffs.get("the_hungerer");
			this.stats.procs_haste += 15/(60+1/(total_hits*1))*1532*the_hungerer;
			
			double the_hungerer_heroic = this.gearbuffs.get("the_hungerer_heroic");
			this.stats.procs_haste += 15/(60+1/(total_hits*1))*1730*the_hungerer_heroic;
			
			double starcatcher_compass = this.gearbuffs.get("starcatcher_compass");
			double starcatcher_compass_heroic = this.gearbuffs.get("starcatcher_compass_heroic");
			double starcatcher_compass_lfr = this.gearbuffs.get("starcatcher_compass_lfr");
			//double starcatcher_compass_uptime = 20/(115+1/(total_hits*0.15));
			this.stats.procs_haste += this.procDB.get("starcatcher_compass").boost(total_hits)*starcatcher_compass;
			this.stats.procs_haste += this.procDB.get("starcatcher_compass_heroic").boost(total_hits)*starcatcher_compass_heroic;
			this.stats.procs_haste += this.procDB.get("starcatcher_compass_lfr").boost(total_hits)*starcatcher_compass_lfr;
			
			double t12_4pc = this.gearbuffs.get("t12_4pc");
			double t12_4pc_uptime = t12_4pc*30./(30+1);
			this.stats.t12_4pc_multiplier = 0.25 * t12_4pc_uptime / 3;
			
		}
		
		// fixed stuff
		
		// find weakness
		double find_weakness_damage_boost = 0;
        double find_weakness_multiplier = 1;
		if (this.stats.talents.get("find_weakness") >= 1){
            double armor_value = 11977;
            double armor_reduction = (1 - .35 * this.stats.talents.get("find_weakness"));
            find_weakness_damage_boost = this.stats.get_eff_dmg(armor_reduction * armor_value) / this.stats.get_eff_dmg();
            find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * find_weakness_uptime;
		}
		
		double time_between_evis = 1 / aps.get("eviscerate");
		double time_between_auto_attacks = 1 / (aps.get("mh")+aps.get("oh"));
		double t11_4_uptime = 1 - Math.pow( (1-0.01) , time_between_evis/time_between_auto_attacks );
		int t11_4pc = this.gearbuffs.get("t11_4pc");
		t11_4_uptime *= t11_4pc;
		
		double dps_mh = mh_swings_per_sec * get_mh_dmg();
		double dps_mh_crit = dps_mh * 2.06 * this.stats.get_crit_chance();
		dps_mh = dps_mh * (this.stats.get_mh_hit_chance(race,mh)-0.24-this.stats.get_crit_chance()) + dps_mh * 0.75 * 0.24 + dps_mh * 2.06 * this.stats.get_crit_chance();
		dps_mh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_mh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein")); 
		dps.put("mh", dps_mh);
		
		double dps_oh = oh_swings_per_sec * get_oh_dmg();
		double dps_oh_crit = dps_oh * 2.06 * this.stats.get_crit_chance();
		dps_oh = dps_oh * (this.stats.get_oh_hit_chance(race,oh)-0.24-this.stats.get_crit_chance()) + dps_oh * 0.75 * 0.24 + dps_oh * 2.06 * this.stats.get_crit_chance();
		dps_oh *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_oh_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein")); 
		dps.put("oh", dps_oh);
		
		double dps_bs = aps.get("backstab") * get_bs_dmg();
		int t11_2pc = gearbuffs.get("t11_2pc");
		double crit_mult = (((2*(1+0.03*1))-1)*(1+0.1*this.stats.talents.get("lethality"))+1);
		double dps_bs_crit = dps_bs * crit_mult * (this.stats.get_crit_chance()+0.1*this.stats.talents.get("puncturing_wounds")+0.05*t11_2pc);
		dps_bs = dps_bs *(1-this.stats.get_crit_chance()-0.1*this.stats.talents.get("puncturing_wounds")-0.05*t11_2pc) + dps_bs * crit_mult * (this.stats.get_crit_chance()+0.1*this.stats.talents.get("puncturing_wounds")+0.05*t11_2pc);
		dps_bs *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_bs_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps.put("backstab", dps_bs);
		
		double find_weakness_ambush_boost = ((1.3 * ambush_shadowstep_rate) + (1 - ambush_shadowstep_rate) * find_weakness_damage_boost);

		double ambush_crit_chance = this.stats.get_crit_chance()+0.2*this.stats.talents.get("imp_ambush");
		if (ambush_crit_chance > 1)
			ambush_crit_chance = 1.;
		double dps_ambush = aps.get("ambush") * get_ambush_dmg();
		double dps_ambush_crit = dps_ambush * 2.06 * ambush_crit_chance;
		dps_ambush = dps_ambush *(1-ambush_crit_chance) + dps_ambush * 2.06 * ambush_crit_chance;
		dps_ambush *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_ambush_boost * (1 + .08 * this.stats.talents.get("sanguinary_vein")) * 1.1; // master of sub
		dps_ambush_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_ambush_boost * (1 + .08 * this.stats.talents.get("sanguinary_vein")) * 1.1; // master of sub
		dps.put("ambush", dps_ambush);
		
		double evi_crit_chance = t11_4_uptime*1+(1-t11_4_uptime)*this.stats.get_crit_chance();
		double dps_evi = aps.get("eviscerate") * get_evi_dmg();
		double dps_evi_crit = dps_evi * 2.06 * evi_crit_chance;
		dps_evi = dps_evi *(1-evi_crit_chance) + dps_evi * 2.06 * evi_crit_chance;
		dps_evi *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_evi_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps.put("eviscerate", dps_evi);
		
		double dps_hemo = aps.get("hemorrhage") * get_hemo_dmg();
		double hemo_crit_mult = (((2*(1+0.03*1))-1)*(1+0.1*this.stats.talents.get("lethality"))+1);
		double dps_hemo_crit = dps_hemo * hemo_crit_mult * (this.stats.get_crit_chance());
		dps_hemo = dps_hemo *(1-this.stats.get_crit_chance()) + dps_hemo * hemo_crit_mult * (this.stats.get_crit_chance());
		dps_hemo *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_hemo_crit *= 1.03 * 1.04 * this.stats.get_eff_dmg() * find_weakness_multiplier * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps.put("hemorrhage", dps_hemo);
		
		double dmg_hemo = dps_hemo / aps.get("hemorrhage");
		double dmg_hemo_crit = dps_hemo_crit / aps.get("hemorrhage");
		double hemo_crit_chance = this.stats.get_crit_chance();
		double hemo_dot_dmg = get_hemo_dot_dmg(dmg_hemo);
		double hemo_dot_dmg_crit = get_hemo_dot_dmg(dmg_hemo_crit);
		double avg_hemo_dot_dmg = hemo_dot_dmg * (1-hemo_crit_chance) + hemo_dot_dmg_crit * hemo_crit_chance;
		
		double dps_hemo_dot = 0.0;
		double dps_hemo_dot_crit = 0.0;
		if(aps.get("hemorrhage_dot") != null){ 
			dps_hemo_dot = aps.get("hemorrhage_dot") * avg_hemo_dot_dmg;
			double hemo_dot_crit_mult = 1.5;
			dps_hemo_dot_crit = dps_hemo_dot * hemo_dot_crit_mult * (this.stats.get_crit_chance());
			dps_hemo_dot = dps_hemo_dot *(1-this.stats.get_crit_chance()) + dps_hemo_dot * hemo_dot_crit_mult * (this.stats.get_crit_chance());
			dps_hemo_dot *= 1.03 * 1.04 * 1.3 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
			dps_hemo_dot_crit *= 1.03 * 1.04 * 1.3 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
			dps.put("hemorrhage_dot", dps_hemo_dot);
		}
		
		double dps_rupture = aps.get("rupture") * get_rupture_dot_dmg();
		double rupture_crit_mult = 1.5;
		double dps_rupture_crit = dps_rupture * rupture_crit_mult * (this.stats.get_crit_chance());
		dps_rupture = dps_rupture *(1-this.stats.get_crit_chance()) + dps_rupture * rupture_crit_mult * (this.stats.get_crit_chance());
		dps_rupture *= 1.03 * 1.04 * 1.3 * (1 + .08 * this.stats.talents.get("sanguinary_vein")) *1.1;
		dps_rupture_crit *= 1.03 * 1.04 * 1.3 * (1 + .08 * this.stats.talents.get("sanguinary_vein")) *1.1;
		dps.put("rupture", dps_rupture);
		
		double dps_dp = aps.get("deadly_poison") * get_dp_dmg(5);
		double dps_dp_crit = dps_dp * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_dp = dps_dp *(1-this.stats.get_spell_crit_chance()) + dps_dp * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_dp *= 1.03 * 1.08 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_dp_crit *= 1.03 * 1.08 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps.put("deadly_poison", dps_dp);
		
		double dps_ip = aps.get("instant_poison") * get_ip_dmg();
		double dps_ip_crit = dps_ip * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_ip = dps_ip *(1-this.stats.get_spell_crit_chance()) + dps_ip * (1.5*(1+0.03)) * this.stats.get_spell_crit_chance();
		dps_ip *= 1.03 * 1.08 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps_ip_crit *= 1.03 * 1.08 * (1 + .08 * this.stats.talents.get("sanguinary_vein"));
		dps.put("instant_poison", dps_ip);
		
		int trinket_hurricane = this.gearbuffs.get("trinket_hurricane");
		double trinket_hurricane_mh_uptime = this.mh.speed*1/60*trinket_hurricane;
		double trinket_hurricane_oh_uptime = this.oh.speed*1/60*trinket_hurricane;
		double mh_hits_per_sec = this.mh_swings_per_sec * this.stats.get_mh_hit_chance(race,mh) + aps.get("eviscerate");
		double oh_hits_per_sec = this.oh_swings_per_sec * this.stats.get_oh_hit_chance(race,oh);
		double trinket_hurricane_mh_procs = trinket_hurricane_mh_uptime*mh_hits_per_sec*this.stats.get_spell_hit_chance();
		double trinket_hurricane_oh_procs = trinket_hurricane_oh_uptime*oh_hits_per_sec*this.stats.get_spell_hit_chance();
		double dps_trinket_hurricane = (trinket_hurricane_mh_procs+trinket_hurricane_oh_procs)*(5250+8750)/2;
		dps_trinket_hurricane *= 1.03;
		if(trinket_hurricane == 1)
			dps.put("trinket_hurricane", dps_trinket_hurricane);
		
		// vial of shadows normal 397
		int trinket_vial_of_shadows_lfr = this.gearbuffs.get("vial_of_shadows_lfr");
		int trinket_vial_of_shadows = this.gearbuffs.get("vial_of_shadows");
		int trinket_vial_of_shadows_heroic = this.gearbuffs.get("vial_of_shadows_heroic");
		double trinket_vos_procs_per_sec = 1 / (9 + 1 / (total_hits * 0.45) );
		double trinket_vos_damage = 5997 + 0.3592 * this.stats.get_ap();
		double trinket_vos_damage_heroic = 6770 + 0.4055 * this.stats.get_ap();
		double trinket_vos_crit_damage = trinket_vos_damage * 2.06;
		double trinket_vos_crit_damage_heroic = trinket_vos_damage_heroic * 2.06;
		double trinket_vos_avg_dmg = trinket_vos_damage * (1-this.stats.get_crit_chance()) + trinket_vos_crit_damage * this.stats.get_crit_chance();
		double trinket_vos_avg_dmg_heroic = trinket_vos_damage_heroic * (1-this.stats.get_crit_chance()) + trinket_vos_crit_damage_heroic * this.stats.get_crit_chance();
		double trinket_vos_dps = trinket_vial_of_shadows * trinket_vos_procs_per_sec * trinket_vos_avg_dmg;
		trinket_vos_dps += trinket_vial_of_shadows_heroic * trinket_vos_procs_per_sec * trinket_vos_avg_dmg_heroic;
		trinket_vos_dps *= 1.03 * 1.04 * this.stats.get_eff_dmg();
		if(trinket_vial_of_shadows == 1 || trinket_vial_of_shadows_heroic == 1)
			dps.put("trinket_vial_of_shadows", trinket_vos_dps);
		
		// nokaled 403
		int no_kaled = this.gearbuffs.get("no_kaled");
		double no_kaled_valid_hits = aps.get("mh") + aps.get("eviscerate");
		double no_kaled_procs_per_sec = 0.07 * no_kaled_valid_hits;
		double no_kaled_damage =  (7654+11481)/2;
		double no_kaled_crit_damage = no_kaled_damage * (1.5*(1+0.03));
		double no_kaled_avg_dmg = no_kaled_damage * (1-this.stats.get_spell_crit_chance()) + no_kaled_crit_damage * this.stats.get_spell_crit_chance();
		double no_kaled_dps = no_kaled * no_kaled_procs_per_sec * no_kaled_avg_dmg;
		no_kaled_dps *= 1.03 * 1.08;
		if(no_kaled == 1)
			dps.put("no_kaled", no_kaled_dps);
		
		// t12_2pc
		int t12_2pc = this.gearbuffs.get("t12_2pc");
		double dps_crit = dps_mh_crit + dps_oh_crit + dps_bs_crit + dps_ambush_crit + dps_hemo_crit + dps_hemo_dot_crit + dps_evi_crit + dps_rupture_crit + dps_dp_crit + dps_ip_crit;
		double dps_t12_2pc = t12_2pc*0.06*dps_crit;
		if(t12_2pc == 1){
			dps.put("burning_wounds", dps_t12_2pc);
		}
		double dps = dps_mh + dps_oh + dps_bs + dps_ambush + dps_hemo + dps_hemo_dot + dps_evi + dps_rupture + dps_dp + dps_ip + dps_trinket_hurricane + trinket_vos_dps + no_kaled_dps + dps_t12_2pc;
		
		this.dps1 = dps;
		return dps;
	}
	
	public void breakdown()
	{
		int max_length = 0;
		for(String name: dps.keySet()) {
			if (name.length() > max_length)
				max_length = name.length();
		}
		for(String name: dps.keySet()) {
			System.out.print(name);
			double ll = max_length - name.length() + 6;
			while(ll > 0) {
				System.out.print(" "); ll--;
			}
			double perc = ((double)Math.round(dps.get(name)/this.dps1*10000))/100;
			System.out.print(dps.get(name) + "   " + perc + "%");
			System.out.println("");
		}
	}
	
	public double get_energy_regen()
	{
		double haste_mod = 0.01*this.stats.get_haste_by_conversion();
		double recuperate_uptime = 1;
		double recuperate_eps = recuperate_uptime * 12 / 3;
		double regen = 10 * (1+haste_mod) + recuperate_eps;
		return regen;
	}
	
	public double get_total_speed()
	{
		double mastery_snd_speed = 1 + .4 * (1 + .025 * this.stats.get_mastery_by_conversion());
		double speed = mastery_snd_speed*(1+0.01*this.stats.get_haste_by_conversion())*1.1*(1+0.3*1*40/300);
		return speed;
	}
	
	public double get_uptime(double proc_chance, int max_stacks,int duration){
		if(proc_chance >= 1)
			return max_stacks;
		double q = 1 - proc_chance * total_hits;
		double Q = Math.pow(q,duration);
		double P = 1 - Q;
		double uptime = P * (1 - Math.pow(P,max_stacks)) / Q;
		return uptime;
	}
	
	public double get_hemo_dmg()
	{
		double damage = this.mh.speed * this.mh.dps + this.mh.get_normalized_speed() * this.stats.get_ap() / 14.;
		double mod = 1;
		double percent_damage_bonus = 2.25*1.4;
		return damage * mod * percent_damage_bonus;
	}
	
	public double get_bs_dmg()
	{
		double damage = this.mh.speed * this.mh.dps + this.mh.get_normalized_speed() * this.stats.get_ap() / 14.;
		double mod = 1 + 0.3;
		double percent_damage_bonus = 2*1.4;
		return (damage + 345) * mod * percent_damage_bonus;
	}
	
	public double get_ambush_dmg()
	{
		double damage = this.mh.speed * this.mh.dps + this.mh.get_normalized_speed() * this.stats.get_ap() / 14.;
		double mod = 1 + 0.3 + 0.15;
		double percent_damage_bonus = 1.9*1.447;
		return (damage + 368) * mod * percent_damage_bonus;
	}
	
	public double get_evi_dmg()
	{
		double[] ranks_aggression = new double[]{0.0,0.07,0.14,0.20};
		double aggression = ranks_aggression[this.stats.talents.get("aggression")];
		double[] ranks_coup_de_grace = new double[]{0.0,0.07,0.14,0.20};
		double coup_de_grace = ranks_coup_de_grace[this.stats.talents.get("coup_de_grace")];
		double damage = ((184+550)/2+(370+this.stats.get_ap()*0.091)*5)*(1+aggression+coup_de_grace + 0.025 * this.stats.get_mastery_by_conversion());
		return damage;
	}
	
	public double get_hemo_dot_dmg(double dmg_hemo)
	{
		double tick_conversion_factor = .4 / 8;
		double tick_damage = dmg_hemo * tick_conversion_factor;
        return tick_damage;
	}
	
	public double get_rupture_dot_dmg()
	{
		double tick_damage = (142 + 20 * 5 + .0375 * this.stats.get_ap())*(1+0.025 * this.stats.get_mastery_by_conversion());
        return tick_damage;
	}
}
